#include <nxos.h>

NX_Error TraceTestEvent(void)
{
    NX_EventRecord record;
    NX_Error err;

    NX_Solt trace = NX_DeviceOpen("e.TestEvent", 0);
    if (trace == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("attach test event failed!\n");
        return NX_EFAULT;
    }
    int i = 0;
    while (1)
    {
        NX_DeviceRead(trace, &record, 0, sizeof(NX_EventRecord));
        err = NX_ErrorGet();
        if (err != NX_EOK && err != NX_EAGAIN)
        {
            break;
        }
        NX_Printf("[%d] header: id=%x, ver=%x, task:%d, kw:%d, time:%d\n", 
            i++, record.header.id, record.header.version, record.header.task, record.header.keyword, record.header.time);
        NX_Printf("data: %s\n", record.data);
    }

    NX_DeviceClose(trace);
    return NX_EOK;
}

NX_PRIVATE void HandleSyscallRecord(NX_EventRecord *record, NX_U32 pid)
{
    NX_UArch *regs;
    regs = (NX_UArch *)record->data;
    if (pid == record->header.task)
    {
        // NX_Printf("header: id=%x, ver=%x, task:%d, kw:%d, time:%d\n", 
        //     record->header.id, record->header.version, record->header.task, record->header.keyword, record->header.time);
        // NX_Printf("size %d, data: %s\n", record->size, record->data);
        return;
    }

    if (record->header.keyword == 1)
    {
        NX_Printf("Task %d SyscallEnter[%d]: args: %x, %x, %x, %x, %x, %x, %x\n", 
            record->header.task, regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]);
    }
    else if (record->header.keyword == 2)
    {
        NX_Printf("Task %d SyscallExit[%d]: return: %x\n", record->header.task, regs[0], regs[1]);
    }
}

NX_PRIVATE void HandleSchedRecord(NX_EventRecord *record, NX_U32 pid)
{
    NX_PRIVATE NX_U64 wakeup = 0;

    // NX_Printf("header: id=%x, ver=%x, task:%d, kw:%d, time:%d\n", 
    //         record->header.id, record->header.version, record->header.task, record->header.keyword, record->header.time);
    // NX_Printf("data: %x\n", record->data[0]);

    if (record->header.keyword == 1)
    {
        // NX_Printf("[%d] SchedSwitch: prev tid: %x, next tid: %x, prev stat: %x\n", 
        //     (NX_U32)record->header.time, regs[0], regs[1], regs[2]);
        if (record->header.task == pid && wakeup)
        {
            NX_U64 delay = record->header.time - wakeup;
            if (delay != 0)
            {
                if (delay > 0)
                {
                    NX_Printf("pid %d sched delay %d\n", pid, delay);
                }
                wakeup = 0;
            }
        }
    }
    else if (record->header.keyword == 2)
    {
        // NX_Printf("[%d] SchedWakeup: tid: %x, cpu: %x\n",
        //     (NX_U32)record->header.time, regs[0], regs[1]);
        if (record->header.task == pid && !wakeup)
        {
            wakeup = record->header.time;
        }
    }
}

NX_Error TraceSyscallEvent(void)
{
    NX_EventRecord records[NX_EVENT_RECORD_MAX_NR];
    NX_Error err;
    NX_U32 pid = NX_ThreadGetCurrentId();
    NX_Size len;

    NX_Solt trace = NX_DeviceOpen("e.Syscall", 0);
    if (trace == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("attach Syscall event failed!\n");
        return NX_EFAULT;
    }

    while (1)
    {
        len = 0;
        NX_MemZero(records, sizeof(records));
        len = NX_DeviceRead(trace, records, 0, NX_EVENT_RECORD_MAX_NR * sizeof(NX_EventRecord));
        err = NX_ErrorGet();
        if (err != NX_EOK && err != NX_EAGAIN) // ok or skip
        {
            break;
        }

        int i;
        for (i = 0; i < len / sizeof(NX_EventRecord); i++)
        {
            HandleSyscallRecord(&records[i], pid);
        }

        NX_ThreadSleep(100);
    }
    NX_Printf("Trace syscall exit!\n");

    NX_DeviceClose(trace);
    return NX_EOK;
}

NX_EventRecord records[NX_EVENT_RECORD_MAX_NR];

NX_Error TraceSchedEvent(void)
{
    NX_Error err;
    NX_U32 pid = 5;
    NX_Size len;

    NX_Solt trace = NX_DeviceOpen("e.Sched", 0);
    if (trace == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("attach Sched event failed!\n");
        return NX_EFAULT;
    }

    while (1)
    {
        len = 0;
        NX_MemZero(records, sizeof(records));
        len = NX_DeviceRead(trace, records, 0, NX_EVENT_RECORD_MAX_NR * sizeof(NX_EventRecord));
        err = NX_ErrorGet();
        if (err != NX_EOK && err != NX_EAGAIN) // ok or skip
        {
            break;
        }

        int i;
        for (i = 0; i < len / sizeof(NX_EventRecord); i++)
        {
            HandleSchedRecord(&records[i], pid);
        }
        NX_ThreadSleep(50);
    }
    NX_Printf("Trace syscall exit!\n");

    NX_DeviceClose(trace);
    return NX_EOK;
}

NX_Error TEST_Trace(char *cmdline)
{
    // return TraceTestEvent();
    return TraceSyscallEvent();
    return TraceSchedEvent();
}
